<?php
declare (strict_types=1);

namespace app\common\model;

use think\Collection;
use Exception;
use think\facade\Cache;

class Category extends CommonBase
{
    private const _CACHE_KEY = '%s_categories';

    // 栏目类型
    public const SINGLE_PAGE         = 1; // 单页
    public const COVER_PAGE          = 2; // 封面
    public const LIST_PAGE           = 3; // 列表
    public const REDIRECT_PAGE       = 4; // 跳转
    public const FIELD_TYPE_NAME_MAP = [
        self::SINGLE_PAGE   => '单页',
        self::COVER_PAGE    => '封面',
        self::LIST_PAGE     => '列表',
        self::REDIRECT_PAGE => '跳转',
    ];

    // 页面模板名称
    public const DEFAULT_TPL = 'cover';
    public const LISTS_TPL   = 'lists';
    public const VIEW_TPL    = 'view';

    // 是否在导航显示
    public const SHOW_IN_NAV       = 1;
    public const HIDE_IN_NAV       = 2;
    public const STATUS_IN_NAV_MAP = [
        self::SHOW_IN_NAV => '显示',
        self::HIDE_IN_NAV => '隐藏',
    ];

    // 关联
    public const IS_ENABLE_RELATION  = 1;
    public const IS_DISABLE_RELATION = 2;

    // 分页
    public const DEFAULT_PAGE_NUM = 20;

    // 栏目打开方式
    public const TOP_TARGET      = 1;
    public const BLANK_TARGET    = 2;
    public const TARGET_NAME_MAP = [self::TOP_TARGET => '当前窗口', self::BLANK_TARGET => '新窗口'];

    public function getAllCategory(string $cdnDomain = '', array $where = []): array
    {
        $cacheKey   = sprintf(self::_CACHE_KEY, $this->connection);
        $categories = Cache::get($cacheKey);
        if (empty($categories)) {
            $categories = $this->where(['is_delete' => config('status.mysql.table_normal')])->order('sort asc,id asc')->select()->toArray();
            Cache::set($cacheKey, $categories, self::CACHE_EXPIRE_TIME);
        }

        $res = [];
        foreach ($categories as $category) {
            $flag = true;
            foreach ($where as $key => $value) {
                if (is_array($value) && count($value) == 3 && $value[1] == 'in' && is_array($value[2])) {
                    if (!in_array($category[$value[0]], $value[2])) {
                        $flag = false;
                        break;
                    }
                } else if ($category[$key] != $value) {
                    $flag = false;
                    break;
                }
            }

            if ($flag) {
                $category['fullImage']          = $cdnDomain . '/' . $category['image'];
                $category['fullNoteImage']      = $cdnDomain . '/' . $category['note_image'];
                $category['fullIntroduceImage'] = $cdnDomain . '/' . $category['introduce_image'];
                $res[$category['id']]           = $category;
            }
        }

        return $res;
    }

    public function getCategoryList(int $page = 1, int $limit = 10, array $where = []): Collection
    {
        $where['is_delete'] = config('status.mysql.table_normal');
        $offset             = ($page - 1) * $limit;
        return $this->where($where)->order('sort asc,id asc')->limit($offset, $limit)->select();
    }

    public function getCategoryCount(array $where = []): int
    {
        $where['is_delete'] = config('status.mysql.table_normal');
        return $this->where($where)->count();
    }

    public function getAllCategoryByIds(array $ids, int $limit = 0)
    {
        $db = $this->where([
            ['id', 'in', $ids],
            'is_delete' => config('status.mysql.table_normal'),
        ]);
        if ($limit > 0) {
            $db = $db->limit($limit);
        }

        return $db->select();
    }

    public function findCategoryById(int $id)
    {
        $categories = $this->getAllCategory();
        return $categories[$id] ?? [];
    }

    public function findCategoryByPid(int $pid)
    {
        return $this->where(['pid' => $pid, 'is_delete' => config('status.mysql.table_normal')])->select();
    }

    /**
     * @throws Exception
     */
    public function addCategory(string $cdnDomain, array $data): bool
    {
        $attributes = $data['attributes'];
        unset($data['attributes']);
        try {
            $this->startTrans();
            $data['is_delete']                = config('status.mysql.table_normal');
            $data['relation_attribute_count'] = count($attributes);
            $data['introduce_content']        = str_replace($cdnDomain, '{cdnDomain}', $data['introduce_content']);
            $data['note']                     = str_replace($cdnDomain, '{cdnDomain}', $data['note']);
            $this->save($data);

            $id = $this->getLastInsID();

            $categoryId = $this->getLastInsID();
            foreach ($attributes as $k => $v) {
                if ($v == 0) {
                    continue;
                }

                $this->name('relation_attribute_category')->save([
                    'is_delete'    => config('status.mysql.table_normal'),
                    'category_id'  => $categoryId,
                    'attribute_id' => $v,
                    'sort'         => $k + 1,
                    'created_at'   => date('Y-m-d H:i:s'),
                    'updated_at'   => date('Y-m-d H:i:s'),
                ]);
            }

            $categoryImages = ['image', 'introduce_image', 'note_image'];
            foreach ($categoryImages as $categoryImage) {
                if (empty($data[$categoryImage])) {
                    continue;
                }
                $this->name('attachment')->where(['url_md5' => md5($data[$categoryImage])])->update([
                    'relation_type' => self::CATEGORY_ATTACHMENT_TYPE,
                    'relation_id'   => $id,
                    'updated_at'    => date('Y-m-d H:i:s'),
                ]);
            }

            $dom                 = new \DOMDocument();
            $editorContentFields = ['introduce_content', 'note'];
            foreach ($editorContentFields as $editorContentField) {
                if (empty($data[$editorContentField])) {
                    continue;
                }

                libxml_use_internal_errors(true);
                $dom->loadHTML($data[$editorContentField]);
                libxml_use_internal_errors(false);
                $xpath   = new \DOMXPath($dom);
                $imgTags = $xpath->query('//img');

                foreach ($imgTags as $imgTag) {
                    $src = ltrim($imgTag->getAttribute('src'), '{cdnDomain}/');
                    $this->name('attachment')->where(['url_md5' => md5($src)])->update([
                        'relation_type' => self::CATEGORY_ATTACHMENT_TYPE,
                        'relation_id'   => $id,
                        'updated_at'    => date('Y-m-d H:i:s'),
                    ]);
                }
            }

            $this->commit();
            Cache::delete(sprintf(self::_CACHE_KEY, $this->connection));
            return true;
        } catch (Exception $e) {
            $this->rollback();
            throw new Exception($e->getMessage());
        }
    }

    /**
     * @throws Exception
     */
    public function updateCategory(string $cdnDomain, int $id, array $data): bool
    {
        $attributes = $data['attributes'];
        unset($data['attributes']);

        try {
            $this->startTrans();
            $data['relation_attribute_count'] = count($attributes);
            $data['updated_at']               = date('Y-m-d H:i:s');
            $data['introduce_content']        = str_replace($cdnDomain, '{cdnDomain}', $data['introduce_content']);
            $data['note']                     = str_replace($cdnDomain, '{cdnDomain}', $data['note']);
            $this->where(['id' => $id])->update($data);

            $this->name('relation_attribute_category')
                ->where(['category_id' => $id])
                ->save(['is_delete' => config('status.mysql.table_delete')]);

            foreach ($attributes as $k => $v) {
                if ($v == 0) {
                    continue;
                }

                $this->name('relation_attribute_category')->save([
                    'is_delete'    => config('status.mysql.table_normal'),
                    'category_id'  => $id,
                    'attribute_id' => $v,
                    'sort'         => $k + 1,
                    'created_at'   => date('Y-m-d H:i:s'),
                    'updated_at'   => date('Y-m-d H:i:s'),
                ]);
            }

            $categoryImages = ['image', 'introduce_image', 'note_image'];
            foreach ($categoryImages as $categoryImage) {
                if (empty($data[$categoryImage])) {
                    continue;
                }
                $this->name('attachment')->where(['url_md5' => md5($data[$categoryImage])])->update([
                    'relation_type' => self::CATEGORY_ATTACHMENT_TYPE,
                    'relation_id'   => $id,
                    'updated_at'    => date('Y-m-d H:i:s'),
                ]);
            }

            $dom                 = new \DOMDocument();
            $editorContentFields = ['introduce_content', 'note'];
            foreach ($editorContentFields as $editorContentField) {
                if (empty($data[$editorContentField])) {
                    continue;
                }

                libxml_use_internal_errors(true);
                $dom->loadHTML($data[$editorContentField]);
                libxml_use_internal_errors(false);
                $xpath   = new \DOMXPath($dom);
                $imgTags = $xpath->query('//img');

                foreach ($imgTags as $imgTag) {
                    $src = ltrim($imgTag->getAttribute('src'), '{cdnDomain}/');
                    $this->name('attachment')->where(['url_md5' => md5($src)])->update([
                        'relation_type' => self::CATEGORY_ATTACHMENT_TYPE,
                        'relation_id'   => $id,
                        'updated_at'    => date('Y-m-d H:i:s'),
                    ]);
                }
            }

            $this->commit();
            Cache::delete(sprintf(self::_CACHE_KEY, $this->connection));
            return true;
        } catch (Exception $e) {
            $this->rollback();
            throw new Exception($e->getMessage());
        }
    }

    /**
     * @throws Exception
     */
    public function deleteCategory(int $id, array $childCategoryIds, string $moduleCode): bool
    {

        try {
            $this->startTrans();

            // 删除栏目
            $this->where(['id' => $id])->save(['is_delete' => config('status.mysql.table_delete')]);

            // 查看文档主表是否有数据，若有数据，需要删除
            $articles = $this->name('article_' . $moduleCode)
                ->where([['category_id', 'in', $childCategoryIds]])
                ->select()
                ->toArray();
            $aids     = [];
            foreach ($articles as $article) {
                $aids[] = $article['id'];
            }

            if (!empty($aids)) {
                // 删除文档主表数据
                $this->name('article_' . $moduleCode)
                    ->where([['category_id', 'in', $childCategoryIds]])
                    ->update(['is_delete'  => config('status.mysql.table_delete'),
                              'updated_at' => date('Y-m-d H:i:s')]);

                // 删除文档扩展表数据
                $tableNames = ['varchar', 'text', 'image', 'flag', 'relation_attribute'];
                foreach ($tableNames as $tableName) {
                    $this->name('article_' . $moduleCode . '_' . $tableName)
                        ->where([['aid', 'in', $aids]])
                        ->update(['is_delete'  => config('status.mysql.table_delete'),
                                  'updated_at' => date('Y-m-d H:i:s')]);
                }
            }

            $this->name('attachment')->where(['relation_id' => $id, 'relation_type' => self::CATEGORY_ATTACHMENT_TYPE])->update([
                'is_delete'  => config('status.mysql.table_delete'),
                'updated_at' => date('Y-m-d H:i:s'),
            ]);

            $this->commit();
            Cache::delete(sprintf(self::_CACHE_KEY, $this->connection));
            return true;
        } catch (Exception $e) {
            $this->rollback();
            throw new Exception($e->getMessage());
        }
    }

    /**
     * @throws Exception
     */
    public function batchUpdateCategorySort(array $data): bool
    {
        try {
            $this->startTrans();
            foreach ($data as $id => $sort) {
                $this->where('id', $id)->update(['sort' => $sort]);
            }

            $this->commit();
            Cache::delete(sprintf(self::_CACHE_KEY, $this->connection));
            return true;
        } catch (Exception $e) {
            $this->rollback();
            throw new Exception($e->getMessage());
        }
    }

    public function clearCategoryCache(): bool
    {
        return Cache::delete(sprintf(self::_CACHE_KEY, $this->connection));
    }
}
